package com.kds.smarthome.service;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import com.kds.smarthome.common.DateType;
import com.kds.smarthome.common.ElectricityValue;
import com.kds.smarthome.common.EnergyType;
import com.kds.smarthome.common.EnergyValue;
import com.kds.smarthome.controller.vo.EnergyFlowData;
import com.kds.smarthome.controller.vo.EnergyFlowItem;
import com.kds.smarthome.controller.vo.EnergyStatsCard;
import com.kds.smarthome.dto.StatisticsItemDTO;
import com.kds.smarthome.entity.Function;
import com.mongodb.client.MongoClient;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

/**
 * @author 孔德嵩
 * 创建日期 2024/12/13
 */
@Service
@Slf4j
public class EnergyService {

    private static final String FIELD_BUILDING_CODE = "building_code";
    private static final String FIELD_TS = "ts";
    private static final String FIELD_ZONGDIAN = "zongdian";
    private static final String SMARTHOME_DB = "smarthome";
    private final Map<String, MongoTemplate> templateCache = new ConcurrentHashMap<>();
    @Autowired
    private MongoClient mongoClient;
    @Autowired
    private StatisticsItemService statisticsItemService;
    @Autowired
    private FunctionService functionService;

    /**
     * 获取指定数据库的 MongoTemplate
     */
    private MongoTemplate getMongoTemplate() {
        return templateCache.computeIfAbsent(EnergyService.SMARTHOME_DB,
                db -> new MongoTemplate(new SimpleMongoClientDatabaseFactory(mongoClient, db)));
    }

    private EnergyStatsCard queryEleEnergyStatsCard(String buildingCode, DateTime now) {
        // 1. 计算时间范围
        DateTime todayStart = now.withTimeAtStartOfDay();
        DateTime yesterdayStart = todayStart.minusDays(1);
        DateTime thisMonthStart = now.withDayOfMonth(1).withTimeAtStartOfDay();
        DateTime lastMonthStart = thisMonthStart.minusMonths(1);

        // 2. 查询各时间段的能耗数据
        Double todayValue = aggregateEnergyConsumption(buildingCode, todayStart.toDate(), now.toDate(), "building_hour");
        Double yesterdayValue = aggregateEnergyConsumption(buildingCode, yesterdayStart.toDate(), now.minusDays(1).toDate(), "building_hour");
        Double thisMonthValue = aggregateEnergyConsumption(buildingCode, thisMonthStart.toDate(), now.toDate(), "building_day");
        Double lastMonthValue = aggregateEnergyConsumption(buildingCode, lastMonthStart.toDate(), now.minusMonths(1).toDate(), "building_day");

        // 3. 构建返回对象
        EnergyStatsCard card = new EnergyStatsCard();
        card.setEnergyType(EnergyType.ele);
        card.setTodayV(todayValue);
        card.setYesterdayV(yesterdayValue);
        card.setThisMonthV(thisMonthValue);
        card.setLastMonthV(lastMonthValue);

        return card;
    }

    /**
     * 聚合计算指定时间范围内的能耗
     *
     * @param buildingCode   建筑编码
     * @param startTime      开始时间
     * @param endTime        结束时间
     * @param collectionName 集合名称
     * @return 聚合结果
     */
    private Double aggregateEnergyConsumption(String buildingCode, Date startTime, Date endTime, String collectionName) {
        try {
            // 获取 smarthome 数据库的 MongoTemplate
            MongoTemplate smartHomeTemplate = getMongoTemplate();

            // 1. 匹配条件
            MatchOperation matchOperation = Aggregation.match(
                    Criteria.where(FIELD_BUILDING_CODE).is(buildingCode)
                            .and(FIELD_TS).gte(startTime).lt(endTime)
            );

            // 2. 分组并求和
            GroupOperation groupOperation = Aggregation.group()
                    .sum(EnergyService.FIELD_ZONGDIAN).as("total");

            // 3. 创建聚合管道
            Aggregation aggregation = Aggregation.newAggregation(
                    matchOperation,
                    groupOperation
            );

            // 4. 执行聚合查询
            AggregationResults<Document> results = smartHomeTemplate.aggregate(
                    aggregation,
                    collectionName,
                    Document.class
            );

            // 5. 获取结果并保留一位小数
            Document result = results.getUniqueMappedResult();
            if (result != null && result.get("total") != null) {
                double value = ((Number) result.get("total")).doubleValue();
                return NumberUtil.round(value, 1).doubleValue();
            }

            return 0.0;
        } catch (Exception e) {
            log.error("聚合计算能耗失败 - buildingCode: {}, startTime: {}, endTime: {}, collection: {}, field: {}, error: {}",
                    buildingCode, startTime, endTime, collectionName, EnergyService.FIELD_ZONGDIAN, e.getMessage(), e);
            return 0.0;
        }
    }

    /**
     * 生成模拟的能源统计数据
     */
    public EnergyStatsCard energyStatsCard(String buildingCode, EnergyType energyType, DateTime now) {


        // 根据不同能源类型生成不同范围的随机数
        switch (energyType) {
            case ele:

                return queryEleEnergyStatsCard(buildingCode, now);

            case water:

            case gas:

                return null;
        }

        return null;
    }


    public List<? extends EnergyValue> queryByEnergyType(String buildingCode, EnergyType energyType,
                                                         DateType dateType, Date startTime, Date endTime) {
        switch (energyType) {
            case ele:
                return queryElectricityData(buildingCode, dateType, startTime, endTime);
//            case water:
//            case gas:
//                return queryBasicEnergyData(buildingCode, energyType,dateType, startTime, endTime);
            default:
                throw new IllegalArgumentException("Unsupported energy type: " + energyType);
        }
    }

    /**
     * 查询电力能耗数据
     *
     * @param buildingCode 建筑编码
     * @param dateType     日期类型（日、月、年）
     * @param startTime    开始时间
     * @param endTime      结束时间
     * @return 电力能耗数据列表
     */
    private List<ElectricityValue> queryElectricityData(String buildingCode, DateType dateType, Date startTime, Date endTime) {
        try {
            // 获取 smarthome 数据库的 MongoTemplate
            MongoTemplate smartHomeTemplate = getMongoTemplate();

            // 根据日期类型确定集合名称和时间提取方式
            String collectionName;
            String dateFormat;
            switch (dateType) {
                case DAY:
                    collectionName = "building_hour";
                    dateFormat = "%H"; // 提取小时 (0-23)
                    break;
                case MONTH:
                    collectionName = "building_day";
                    dateFormat = "%d"; // 提取日期 (1-31)
                    break;
                case YEAR:
                    collectionName = "building_month";
                    dateFormat = "%m"; // 提取月份 (1-12)
                    break;
                default:
                    throw new IllegalArgumentException("Unsupported date type: " + dateType);
            }

            // 1. 匹配条件
            MatchOperation matchOperation = match(
                    Criteria.where(FIELD_BUILDING_CODE).is(buildingCode)
                            .and(FIELD_TS).gte(startTime).lt(endTime)
            );

            // 2. 提取时间索引并投影所需字段
            ProjectionOperation projectOperation = project()
                    .andExpression(String.format("{ $toInt: { $dateToString: { format: '%s', date: '$ts', timezone: '+08:00' } } }", dateFormat)).as("index")
                    .and("zongdian").as("zongdian")
                    .and("zhaoming").as("zhaoming")
                    .and("kongtiao").as("kongtiao")
                    .and("dongli").as("dongli")
                    .and("teshu").as("teshu");

            // 3. 创建聚合管道
            Aggregation aggregation = newAggregation(
                    matchOperation,
                    projectOperation,
                    // 4. 按时间索引排序
                    sort(Sort.Direction.ASC, "index")
            );

            // 5. 执行聚合查询
            AggregationResults<ElectricityValue> results = smartHomeTemplate.aggregate(
                    aggregation,
                    collectionName,
                    ElectricityValue.class
            );

            List<ElectricityValue> values = results.getMappedResults();

            // 6. 填充缺失的时间点（设置为0值）
            List<ElectricityValue> completeValues = new ArrayList<>();
            int maxIndex = getMaxIndex(dateType, startTime);

            for (int i = 0; i <= maxIndex; i++) {
                ElectricityValue value = findValueByIndex(values, i);
                if (value == null) {
                    value = new ElectricityValue();
                    value.setIndex(i);
                    value.setZongdian(0.0);
                    value.setZhaoming(0.0);
                    value.setKongtiao(0.0);
                    value.setDongli(0.0);
                    value.setTeshu(0.0);
                }
                completeValues.add(value);
            }

            return completeValues;

        } catch (Exception e) {
            log.error("查询电力能耗数据失败 - buildingCode: {}, dateType: {}, startTime: {}, endTime: {}, error: {}",
                    buildingCode, dateType, startTime, endTime, e.getMessage(), e);
            return new ArrayList<>();
        }
    }

    /**
     * 根据索引查找对应的值
     */
    private ElectricityValue findValueByIndex(List<ElectricityValue> values, int index) {
        return values.stream()
                .filter(v -> v.getIndex() == index)
                .findFirst()
                .orElse(null);
    }

    /**
     * 获取最大索引值
     */
    private int getMaxIndex(DateType dateType, Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        switch (dateType) {
            case DAY:
                return 23; // 小时: 0-23
            case MONTH:
                return cal.getActualMaximum(Calendar.DAY_OF_MONTH) - 1; // 日期: 1-31
            case YEAR:
                return 11; // 月份: 1-12
            default:
                throw new IllegalArgumentException("Invalid date type: " + dateType);
        }
    }

    private int getDataPoints(DateType dateType, Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        switch (dateType) {
            case YEAR:
                return 8;  // 12个月
            case MONTH:
                return cal.getActualMaximum(Calendar.DAY_OF_MONTH);  // 获取当月实际天数
            case DAY:
                return 14;  // 24小时
            default:
                throw new IllegalArgumentException("Invalid date type: " + dateType);
        }
    }

    private int getStartIndex(DateType dateType) {
        switch (dateType) {
            case YEAR:
                return 1;  // 月份从1开始
            case MONTH:
                return 1;  // 日期从1开始
            case DAY:
                return 0;  // 小时从0开始
            default:
                throw new IllegalArgumentException("Invalid date type: " + dateType);
        }
    }

    /**
     * 计算平均面积能耗
     */
    public double calculateAverageArea(String buildingCode) {
        // 这里添加实际的计算逻辑
        return RandomUtil.randomDouble(65, 75); // 暂时返回模拟数据
    }

    /**
     * 计算能效比
     */
    public double calculateEER(String buildingCode) {
        // 这里添加实际的计算逻辑
        return RandomUtil.randomDouble(6.2, 6.5); // 暂时返回模拟数据
    }

    /**
     * 获取能源流向数据
     */
    public EnergyFlowData getEnergyFlow(String buildingCode) {
        List<StatisticsItemDTO> statisticsItemDTOS = statisticsItemService.queryStatisticsItems(buildingCode);
        List<Function> energyFunctions = functionService.selectEnergyFunctions(buildingCode);

        EnergyFlowData flowData = new EnergyFlowData();

        try {
            MongoTemplate mongoTemplate = getMongoTemplate();

            // 1. 从 statisticsItemDTOS 中提取所有 fids
            List<String> allFids = statisticsItemDTOS.stream()
                    .flatMap(item -> item.getFids().stream())
                    .collect(Collectors.toList());

            // 2. 创建 fid 到 StatisticsItemDTO 的映射
            Map<String, StatisticsItemDTO> fidToItemMap = new HashMap<>();
            for (StatisticsItemDTO item : statisticsItemDTOS) {
                for (String fid : item.getFids()) {
                    fidToItemMap.put(fid, item);
                }
            }

            // 3. 创建 fid 到 Function 的映射
            Map<String, Function> fidToFunctionMap = energyFunctions.stream()
                    .collect(Collectors.toMap(Function::getFid, function -> function));

            // 4. 查询建筑总能耗数据
            TypedAggregation<Document> buildingAggregation = newAggregation(Document.class,
                    match(Criteria.where(FIELD_BUILDING_CODE).is(buildingCode)),
                    group()
                            .sum("zongdian").as("zongdian")
                            .sum("zhaoming").as("zhaoming")
                            .sum("kongtiao").as("kongtiao")
                            .sum("dongli").as("dongli")
                            .sum("teshu").as("teshu")
            );

            AggregationResults<Map> buildingResults = mongoTemplate.aggregate(
                    buildingAggregation,
                    "building_day",
                    Map.class
            );

            Map buildingStats = buildingResults.getUniqueMappedResult();
            if (buildingStats == null) {
                buildingStats = new HashMap<>();
            }

            // 5. 构建分类数据
            List<EnergyFlowItem> items = new ArrayList<>();
            items.add(new EnergyFlowItem("总用电", "zongdian", getDoubleValue(buildingStats.get("zongdian"))));
            for (StatisticsItemDTO item : statisticsItemDTOS) {
                items.add(new EnergyFlowItem(
                        item.getName(),
                        item.getCode(),
                        getDoubleValue(buildingStats.get(item.getCode()))
                ));
            }
            flowData.setItems(items);

            if (!allFids.isEmpty()) {
                // 6. 查询仪表数据
                TypedAggregation<Document> meterAggregation = newAggregation(Document.class,
                        match(Criteria.where("fid").in(allFids)),
                        group("fid")
                                .sum("value").as("value")
                );

                AggregationResults<Map> meterResults = mongoTemplate.aggregate(
                        meterAggregation,
                        "meter_day",
                        Map.class
                );

                // 7. 构建功能点数据
                List<EnergyFlowItem> functions = new ArrayList<>();
                for (Map result : meterResults.getMappedResults()) {
                    String fid = (String) result.get("_id");
                    Double value = getDoubleValue(result.get("value"));

                    // 使用 Function 中的 name
                    Function function = fidToFunctionMap.get(fid);
                    StatisticsItemDTO item = fidToItemMap.get(fid);
                    if (function != null && item != null) {
                        functions.add(new EnergyFlowItem(function.getName(), item.getCode(), value));
                    }
                }

                flowData.setFunctions(functions);
            }

            log.info("成功获取能源流向数据 - buildingCode: {}, 分类数: {}, 功能点数: {}",
                    buildingCode, items.size(), flowData.getFunctions() != null ? flowData.getFunctions().size() : 0);

        } catch (Exception e) {
            log.error("获取能源流向数据失败 - buildingCode: {}, 错误: {}", buildingCode, e.getMessage(), e);
        }

        return flowData;
    }

    /**
     * 安全获取 Double 值，并取整
     */
    private double getDoubleValue(Object value) {
        if (value == null) {
            return 0.0;
        }
        if (value instanceof Number) {
            return Math.round(((Number) value).doubleValue());
        }
        try {
            return Math.round(Double.parseDouble(value.toString()));
        } catch (Exception e) {
            return 0.0;
        }
    }




    public String calculateDailyStatistics(String buildingCode, Integer days) {

        System.out.println("------------------------------------------");

        return buildingCode+"   "+days;
    }
}

class Document {
    private Object total;

    public Object get(String field) {
        return total;
    }
}
